﻿Module BFVM
    Const ver = 0.7
    Const configfile = "BFVM.ini"
    Const hMessage = "Official website: http://jinzihao.info" & vbCrLf & vbCrLf & "Brainfuck extended commands:" & vbCrLf & "! : Beep" & vbCrLf & "{command}*{number} : repeat a {command} for {number} times. For example: " & vbCrLf & "+*5 is equal to +++++" & vbCrLf & ">*4 is equal to >>>>" & vbCrLf & "However +->*3 is equal to +->>> but not +->+->+->" & vbCrLf & vbCrLf & "Interpreter commands, which are case insensitive, are listed below:" & vbCrLf & "HELP: Show this help message" & vbCrLf & "LOADPROG {filename}: Load a Brainfuck program from {filename} and run it immediately" & vbCrLf & "CLS (or CLEAR): Clear screen" & vbCrLf & "EXIT: Quit Brainfuck VM" & vbCrLf & "PROMPT {ON/OFF}: Turn {on}/{off} prompt of the virtual machine" & vbCrLf & "EXTCOM {ON/OFF}: Turn {on}/{off} Brainfuck extended commands interpreter" & vbCrLf & "DUMPDATA {filename}: Dump the memory of Brainfuck VM to {filename}." & vbCrLf & "LOADDATA {filename}: Load a memory file from {filename}"
    Dim data(30000) As Byte
    Dim pointer As Integer
    'Global config
    Dim extCommands As Boolean = True
    Dim prompt As Boolean = True

    Sub Main()
        'Initialize
        Dim c As String
        Dim ch As Char
        Dim splace As Integer
        Dim eplace As Integer
        Dim tStr As String
        Dim i As Integer = 1
        Dim fn As String
        Dim cfg As String
        Dim converted As Boolean
        Dim tempint As Integer
        If My.Computer.FileSystem.FileExists(configfile) = False Then
            FileOpen(1, configfile, OpenMode.Output)
            PrintLine(1, "extCommands=" & True)
            PrintLine(1, "prompt=" & True)
            FileClose(1)
        Else
            FileOpen(1, configfile, OpenMode.Input)
            While Not EOF(1)
                cfg = LineInput(1)
                For i = 1 To Len(cfg)
                    If Mid(cfg, i, 1) = "=" Then GoTo LoadConfig
                Next
LoadConfig:
                Select Case UCase(Left(cfg, i - 1))
                    Case "EXTCOMMANDS"
                        extCommands = Right(cfg, Len(cfg) - i)
                    Case "PROMPT"
                        prompt = Right(cfg, Len(cfg) - i)
                End Select
            End While
            FileClose(1)
        End If
        Console.Title = "Brainfuck Virtual Machine v" & CStr(ver)
        Console.WriteLine("Brainfuck Virtual Machine v" & CStr(ver))
        Console.WriteLine("For help, simply type help")
Start:
        While Not UCase(c) = "EXIT"
            If prompt = True Then Console.Write("@" & CStr(pointer) & ":" & data(pointer) & "> ")
            c = Console.ReadLine()
            'Interpreter Level 1
            'Interpreter commands
            converted = False
            If UCase(c) = "HELP" Then
                Console.WriteLine(hMessage)
            ElseIf UCase(Left(c, 8)) = "LOADPROG" Then
                For i = 1 To Len(c)
                    If Mid(c, i, 1) = " " Then
                        fn = Mid(c, i + 1, Len(c) - i)
                        GoTo Load
                    End If
                Next
                Console.WriteLine("Invalid command")
                GoTo Start
Load:
                c = ""
                FileOpen(1, fn, OpenMode.Input)
                While Not EOF(1)
                    c = c & LineInput(1)
                End While
                Console.Write("Loaded " & FileLen(fn) & " bytes  ")
                FileClose(1)
                Console.WriteLine(c)
                GoTo Preprocess
            ElseIf UCase(c) = "CLS" Then
                Console.Clear()
            ElseIf UCase(c) = "CLEAR" Then
                Console.Clear()
            ElseIf UCase(c) = "PROMPT ON" Then
                prompt = True
                WriteConfig()
            ElseIf UCase(c) = "PROMPT OFF" Then
                prompt = False
                WriteConfig()
            ElseIf UCase(c) = "PROMPT" Then
                Console.WriteLine(prompt)
            ElseIf UCase(c) = "EXTCOM ON" Then
                extCommands = True
                WriteConfig()
            ElseIf UCase(c) = "EXTCOM OFF" Then
                extCommands = False
                WriteConfig()
            ElseIf UCase(c) = "EXTCOM" Then
                Console.WriteLine(extCommands)
            ElseIf UCase(Left(c, 8)) = "DUMPDATA" Then
                For i = 1 To Len(c)
                    If Mid(c, i, 1) = " " Then
                        fn = Mid(c, i + 1, Len(c) - i)
                        GoTo DUMPDATA
                    End If
                Next
                Console.WriteLine("Invalid command")
                GoTo Start
DUMPDATA:
                c = ""
                FileOpen(1, fn, OpenMode.Output)
                For i = 0 To UBound(data)
                    Write(1, data(i))
                Next
                FileClose(1)
            ElseIf UCase(Left(c, 8)) = "LOADDATA" Then
                For i = 1 To Len(c)
                    If Mid(c, i, 1) = " " Then
                        fn = Mid(c, i + 1, Len(c) - i)
                        GoTo LOADDATA
                    End If
                Next
                Console.WriteLine("Invalid command")
                GoTo Start
LOADDATA:
                c = ""
                FileOpen(1, fn, OpenMode.Input)
                For i = 0 To UBound(data)
                    Input(1, tempint)
                    data(i) = CByte(tempint)
                Next
                FileClose(1)
            Else
Preprocess:
                'Interpreter Level 2
                'Preprocess BF code in order to translate extended commands to standard BF command
                If extCommands = False Then GoTo 2
                i = 1
0:
                ch = Mid(c, i, 1)
                tStr = ""
                If ch = "*" Then
                    For j = i To Len(c)
                        If Mid(c, j, 1) = ">" Or Mid(c, j, 1) = "<" Or Mid(c, j, 1) = "+" Or Mid(c, j, 1) = "-" Or Mid(c, j, 1) = "." Or Mid(c, j, 1) = "," Or Mid(c, j, 1) = "[" Or Mid(c, j, 1) = "]" Then
                            eplace = j - 1
                            GoTo 1
                        End If
                    Next
                    eplace = Len(c)
1:
                    For k = 1 To CInt(Mid(c, i + 1, eplace - i))
                        tStr = tStr & Mid(c, i - 1, 1)
                    Next
                    c = Left(c, i - 2) & tStr & Right(c, Len(c) - eplace)
                    converted = True
                End If
                i = i + 1
                If i < Len(c) Then GoTo 0
                If converted = True Then
                    Console.WriteLine("Converted code:")
                    Console.WriteLine(c)
                End If
2:
                'Interpreter Level 3
                'Main Interpreter
                splace = 1
3:
                For i = splace To Len(c)
                    ch = Mid(c, i, 1)
                    Select Case ch
                        '<Extended commands>
                        Case "!"
                            If extCommands = True Then Beep()
                            '</Extended commands>
                        Case ">"
                            If pointer < UBound(data) Then pointer = pointer + 1 Else pointer = 0
                        Case "<"
                            If pointer > 0 Then pointer = pointer - 1 Else pointer = 30000
                        Case "+"
                            If data(pointer) < 255 Then data(pointer) = data(pointer) + 1 Else data(pointer) = 0
                        Case "-"
                            If data(pointer) > 0 Then data(pointer) = data(pointer) - 1 Else data(pointer) = 255
                        Case "."
                            Console.WriteLine(Chr(data(pointer)))
                        Case ","
                            data(pointer) = Asc(Console.ReadKey.KeyChar)
                        Case "["
                            If data(pointer) = 0 Then
                                For j = i To Len(c)
                                    If Mid(c, j, 1) = "]" Then
                                        splace = j + 1
                                        GoTo 3
                                    End If
                                Next
                            End If
                        Case "]"
                            If Not data(pointer) = 0 Then
                                For j = i To 1 Step -1
                                    If Mid(c, j, 1) = "[" Then
                                        splace = j + 1
                                        GoTo 3
                                    End If
                                Next
                            End If
                    End Select
                Next
            End If
        End While
    End Sub

    Function WriteConfig()
        FileOpen(1, configfile, OpenMode.Output)
        PrintLine(1, "extCommands=" & extCommands)
        PrintLine(1, "prompt=" & prompt)
        FileClose(1)
        WriteConfig = True
    End Function
End Module
